Skip to content

release: v0.1.1 - #7

Merged
kolkov merged 5 commits into
mainfrom
release/v0.1.1
Jul 15, 2026
Merged

kolkov merged 5 commits into
mainfrom
release/v0.1.1

Conversation

@kolkov

@kolkov kolkov commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

v0.1.1 — Subscribe lifecycle fix + allocation optimization

Fixed

Performance

  • Signal_Subscribe: 745 ns → 134 ns (5.6x faster), 224B → 52B (−77% memory)
  • Effect_Create (1 dep): 1560 ns → 352 ns (4.4x faster), 384B → 196B, 10 → 7 allocs
  • Effect_Create (3 deps): 4620 ns → 726 ns (6.4x faster), 1016B → 388B, 24 → 13 allocs
  • Effect_Stop: OOM → 330 ns (goroutine accumulation eliminated)
  • AsReadonly(): cached after first call (zero allocs on subsequent calls)

Added

  • Allocation regression tests using testing.AllocsPerRun — deterministic, breaks
    build if heap allocations increase
  • Subscribe lifecycle regression tests — goroutine leak detection (10k held subs)
    and concurrent cancel+unsub race condition
  • Cached AsReadonly() — returns same pointer on every call
  • Dual-path Effect — avoids wrapper closure allocation for effects without cleanup

Removed

  • scripts/ directory (unused pre-release check scripts)

Test plan

  • 69 tests passing (59 existing + 10 alloc assertions)
  • Race detector clean (CI on ubuntu/macOS/windows)
  • Goroutine leak regression: 10k held subs = 0 goroutines
  • Concurrent cancel+unsub: no panic under -race
  • All benchmarks show improvement, zero regressions
  • go vet clean, gofmt clean

Closes #6

kolkov added 5 commits July 15, 2026 13:54
…race

Replace goroutine+done channel pattern in Subscribe with
context.AfterFunc + atomic.Bool.CompareAndSwap.

Fixes two bugs (issue #6, reported by @AnyCPU):

1. Goroutine leak: each Subscribe spawned a goroutine that blocked
   on select{ctx.Done, done} for the subscription lifetime.
   SubscribeForever (context.Background) parked it forever.
   10k subscriptions = 10k idle goroutines (20-40MB stack waste).

2. Race condition: concurrent cancel() + Unsubscribe() could both
   reach close(done) via non-atomic select-default guard, causing
   panic: close of closed channel.

context.AfterFunc registers cleanup without a goroutine; for
context.Background (Done()==nil) nothing is registered at all.
atomic.Bool.CompareAndSwap makes cleanup idempotent without the
extra heap allocation sync.Once requires.

Signal_Subscribe: 745ns/224B/3allocs -> 192ns/52B/2allocs (3.9x)
Effect_Stop benchmark: OOM -> 552ns (goroutine accumulation gone)
Goroutines per 10k held subs: 10000 -> 0

Closes #6
Four independent optimizations:

- Pre-allocate unsubscribes slice with known dep count
- Cache AsReadonly() in signal struct (lazy init, same pointer on every call)
- Capture e.run method value once before dep loop (avoids N closures for N deps)
- Dual-path Effect: store fnSimple directly, skip wrapper closure allocation

Effect_Create(1dep): 1560ns/384B/10allocs -> 352ns/196B/7allocs (4.4x)
Effect_Create(3deps): 4620ns/1016B/24allocs -> 726ns/388B/13allocs (6.4x)
Signal_Subscribe: 745ns/224B/3allocs -> 134ns/52B/2allocs (5.6x)

Remaining 7 allocs are near-irreducible: effect struct, subscribe
closure+atomic, type-erased callback, method value, slice header.
10 deterministic allocation-count tests that break the build
if heap allocations increase:

- Signal: Get(0), Set(0), Set+subscriber(1), SubscribeForever(2), Subscribe+ctx(5)
- Computed: Get cached(0), SubscribeForever(2)
- AsReadonly cached(0)
- Effect(7), EffectWithCleanup(7)

Unlike benchmark ns/op, AllocsPerRun counts are deterministic
and will catch any allocation regression immediately in CI.
- README: update test count (69), coverage (93.8%), benchmarks
  with post-optimization numbers, remove make/scripts references
- CHANGELOG: add v0.1.1 entry (goroutine leak fix, race fix,
  5.6x Subscribe speedup, allocation optimizations)
- Remove scripts/ directory (unused)
- Add tmp/ to .gitignore
- Remove /scripts/ from CODEOWNERS
docs/dev/ is in .gitignore — links to it from README and
CONTRIBUTING were dead for anyone cloning the repo. Replace
with pkg.go.dev reference and inline design notes.
@kolkov
kolkov merged commit b2656d8 into main Jul 15, 2026
6 checks passed
@kolkov
kolkov deleted the release/v0.1.1 branch July 15, 2026 12:15
@codecov

codecov Bot commented Jul 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 84.31373% with 8 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
effect.go 83.33% 2 Missing and 3 partials ⚠️
computed.go 66.66% 2 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

signals: drop per-Subscribe goroutine (context.AfterFunc); Once-guard Unsubscribe

1 participant